/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
package com.bigdata.btree.raba;

import java.io.DataInput;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A flyweight mutable implementation exposing the backing byte[][], permitting
 * <code>null</code>s and not supporting search. It is assumed that caller
 * maintains a dense byte[][] in the sense that all entries in [0:nvalues] are
 * defined, even if some of entries are null. The implementation is NOT
 * thread-safe for mutation.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public class MutableValueBuffer implements IRaba {

    /**
     * The #of entries with valid data.
     */
    public int nvalues;
    
    /**
     * The backing array.
     */
    public final byte[][] values;

    /**
     * Mutable.
     */
    final public boolean isReadOnly() {

        return false;
        
    }

    /**
     * For B+Tree values.
     */
    @Override
    final public boolean isKeys() {
     
        return false;
        
    }

    /**
     * Create a new, empty byte[][] of the specified capacity.
     * 
     * @param nvalues
     *            The capacity of the byte[][].
     */
    public MutableValueBuffer(final int nvalues) {
        
        this(0/* size */, new byte[nvalues][]);
        
    }

    /**
     * Create a view of a byte[][]. All elements in the array are visible in the
     * view.
     * 
     * @param nvalues
     *            The #of entries in the array with valid data.
     * @param values
     *            The backing byte[][].
     */
    public MutableValueBuffer(final int nvalues, final byte[][] values) {

        if (values == null)
            throw new IllegalArgumentException();

        if (nvalues < 0 || nvalues >= values.length)
            throw new IllegalArgumentException();
        
        this.nvalues = nvalues;

        this.values = values;

    }

    /**
     * Builds a mutable value buffer.
     * 
     * @param capacity
     *            The capacity of the new instance (this is based on the
     *            branching factor for the B+Tree).
     * @param src
     *            The source data.
     * 
     * @throws IllegalArgumentException
     *             if the capacity is LT the {@link IRaba#size()} of the
     *             <i>src</i>.
     * @throws IllegalArgumentException
     *             if the source is <code>null</code>.
     */
    public MutableValueBuffer(final int capacity, final IRaba src) {

        if (src == null)
            throw new IllegalArgumentException();
        
        if (capacity < src.capacity())
            throw new IllegalArgumentException();
        
        nvalues = src.size();

        assert nvalues >= 0; // allows deficient root.

        values = new byte[capacity][];

        int i = 0;
        for (byte[] a : src) {

            values[i++] = a;

        }
        
    }

    @Override
    final public int size() {

        return nvalues;
        
    }

    @Override
    final public boolean isEmpty() {
        
        return nvalues == 0;
        
    }
    
    @Override
    final public boolean isFull() {

        return nvalues == values.length;
        
    }
    
    @Override
    final public int capacity() {
        
        return values.length;
        
    }

    final protected boolean rangeCheck(final int index)
            throws IndexOutOfBoundsException {

        if (index < 0 || index >= nvalues) {

            throw new IndexOutOfBoundsException("index=" + index
                    + ", capacity=" + nvalues);

        }

        return true;

    }
    
    final public byte[] get(final int index) {

        assert rangeCheck(index);
        
        return values[index];
        
    }

    @Override
    final public int length(final int index) {

        assert rangeCheck(index);

        final byte[] tmp = values[index];

        if (tmp == null)
            throw new NullPointerException();

        return tmp.length;

    }

    @Override
    final public boolean isNull(final int index) {

        assert rangeCheck(index);

        return values[index] == null;

    }
    
    @Override
    final public int copy(final int index, final OutputStream out) {

        assert rangeCheck(index);

        final byte[] tmp = values[index];

        if (tmp == null)
            throw new NullPointerException();

        try {

            out.write(tmp, 0, tmp.length);
            
        } catch (IOException ex) {
            
            throw new RuntimeException(ex);
            
        }
        
        return tmp.length;
        
    }
    
    @Override
    final public Iterator<byte[]> iterator() {

        return new Iterator<byte[]>() {

            int i = 0;

            @Override
            public boolean hasNext() {

                return i < nvalues;

            }

            @Override
            public byte[] next() {

                if (!hasNext())
                    throw new NoSuchElementException();

                return values[i++];

            }

            @Override
            public void remove() {

                if (isReadOnly())
                    throw new UnsupportedOperationException();

                // @todo support remove on the iterator when mutable.
                throw new UnsupportedOperationException();
                
            }

        };

    }
    
    /**
     * @throws IllegalStateException
     *             unless there is room to store another value.
     */
    protected void assertNotFull() {

        if (nvalues >= values.length) {

            throw new IllegalStateException();
            
        }

    }
    
    @Override
    public void set(final int index, final byte[] key) {
        
        assert rangeCheck(index);

        values[index] = key;
        
    }

    @Override
    public int add(final byte[] key) {

        assertNotFull();
        
        values[nvalues++] = key;
        
        return nvalues;
        
    }
    
    @Override
    public int add(final byte[] key, final int off, final int len) {

        assertNotFull();

        final byte[] b = new byte[len];

        System.arraycopy(key, off, b, 0, len);
        
        values[nvalues++] = b;

        return nvalues;

    }
    
    @Override
    public int add(final DataInput in, final int len) throws IOException {
        
        assertNotFull();

        final byte[] b = new byte[len];

        in.readFully(b, 0, len);

        values[nvalues++] = b;

        return nvalues;

    }

    @Override
    final public int search(final byte[] searchKey) {

        throw new UnsupportedOperationException();
       
    }
    
    @Override
    public String toString() {

        return AbstractRaba.toString(this);

    }
 
}
